﻿#nowarn "25"

namespace VisualPhysics.Scene.Core

open System.Drawing
open VisualPhysics.Scene.Core.Operators

/// The RopeObject type represents a Rope.
type RopeObject private (sideA: Object, sideB: Object, mass: float<gram>, color: Brush, lastUpdate: float<s>, dummy: unit) =
    /// <summary>Creates a new rope with the given parameters.</summary>
    /// <param name="SideA">The object connected to the first end of the rope.</param>
    /// <param name="SideB">The object connected to the second end of the rope.</param>
    /// <param name="Mass">The mass in gram.</param>
    /// <param name="Color">The color as a System.Drawing.Brush.</param>
    new (sideA, sideB, mass, ?color, ?lastUpdate) =
        let color = defaultArg color Brushes.Black
        let lastUpdate = defaultArg lastUpdate 0.0<s>
        RopeObject(sideA, sideB, mass, color, lastUpdate, ())
        
    /// <summary>Creates simultaneously a group of ropes which match the given parameters.</summary>
    /// <param name="List">A list of data about each rope.</param>
    /// <returns>An option for a list of ropes and a temporary hook.<returns>
    static member Create (list : (Object * float<cm> option * float<gram> * Brush option) list) (time: float<s>) =
        let withLength =
            list
            |> List.filter (fun (_, o, _, _) ->
                                            match o with
                                            | Some(_) -> true
                                            | None -> false)
        
        let getPairs list =
            list
            |> List.map (fun n ->
                            list
                            |> List.filter (fun m -> m <> n)
                            |> List.map (fun m -> (n, m)))
            |> List.concat

        let possible =
            withLength
            |> getPairs
            |> List.forall (fun ((o1 : Object, ValueOnly(l1), _, _), (o2 : Object, ValueOnly(l2), _, _)) ->
                                distance o1.CarPosition o2.CarPosition < l1 + l2)

        if possible then
            let intersection =
                match List.length withLength with
                | 0 ->
                    let (o, _, _, _)::_ = list
                    Some(o.X + 0.5<cm>, o.Y + 0.5<cm>)
                | 1 ->
                    let (o, ValueOnly(l), _, _)::_ = withLength
                    let x, y = o.CarPosition
                    Some(x + sqrt (l * l / 2.0), y + sqrt (l * l / 2.0))
                | _ ->
                    let (o1, ValueOnly(lA), _, _)::(o2, ValueOnly(lB), _, _)::_ = withLength
                    let distanceBetweenCores = distance o1.CarPosition o2.CarPosition
                    let centerLineTeta = asin2 (o2.Y - o1.Y) distanceBetweenCores
                    let relativeTeta = acos2 (lB * lB + distanceBetweenCores * distanceBetweenCores - lA * lA) (2.0 * lB * distanceBetweenCores)
                    let teta = centerLineTeta + relativeTeta
                    let is = lA * cos teta + o1.X, lA * sin teta + o1.Y
                    let correct =
                        withLength
                        |> List.forall (fun (o, ValueOnly(l), _, _) ->
                                            abs((distance is o.CarPosition) - l) < 0.000001<cm>)
                    if correct then
                        Some is
                    else
                        let relativeTeta = 2.0 * System.Math.PI - relativeTeta
                        let teta = centerLineTeta + relativeTeta
                        let is = lA * cos teta + o1.X, lA * sin teta + o1.Y
                        let correct =
                            withLength
                            |> List.forall (fun (o, ValueOnly(l), _, _) ->
                                                abs((distance is o.CarPosition) - l) < 0.000001<cm>)

                        if correct then
                            Some is
                        else
                            None
            
            match intersection with
            | Some is ->
                let hook = HookObject(pol is, lastUpdate = time)
                let list =
                    list
                    |> List.map (fun (o, lo, m, co) ->
                                    match co with
                                    | None ->
                                        RopeObject(o, hook, m)
                                    | Some(c) ->
                                        RopeObject(o, hook, m, c))
                Some(list, hook)
            | None ->
                None
        else
            None

    /// The position of the first end of the rope.
    member this.CarStart =
        match sideA.Tag with
        | Box
        | StaticBox ->
            let topx, topy = sideA.CarPosition
            let rawdelta = (sideA :?> BoxObject).Size
            let teta = (sideA :?> BoxObject).Rotation
            let dx, dy = rotate rawdelta teta
            topx + dx, topy + dy
        | Rope ->
            failwith "Cannot be attached to a rope."
        | Ball
        | Hook ->
            sideA.CarPosition
    /// The position of the second end of the rope.
    member this.CarEnd =
        match sideB.Tag with
        | Box
        | StaticBox ->
            let topx, topy = sideA.CarPosition
            let rawdelta = (sideA :?> BoxObject).Size
            let teta = (sideA :?> BoxObject).Rotation
            let dx, dy = rotate rawdelta teta
            topx + dx, topy + dy
        | Rope ->
            failwith "Cannot be attached to a rope."
        | Ball
        | Hook ->
            sideB.CarPosition
    /// The length of the rope
    member this.Length =
        distance this.CarStart this.CarEnd

    interface Object with
        /// The tag of the rope - Rope
        member this.Tag = Rope
        /// The time of the last update.
        member this.LastSync = lastUpdate
        /// The position of the rope as a tuple in centimeters.
        member this.CarPosition = this.CarStart
        /// The position of the rope as an MVector of cm centimeters.
        member this.PolPosition = pol this.CarStart
        /// The position of the rope on the X axis in centimeters.
        member this.X = fst this.CarStart
        /// The position of the rope on the Y axis in centimeters.
        member this.Y = snd this.CarStart
        /// No velocity of rope.
        member this.Velocity = System.Double.PositiveInfinity * 1.0<cm / s>, System.Double.PositiveInfinity
        /// The color of the rope (System.Drawing.Brush).
        member this.Color = color
        /// The mass of the rope in gram.
        member this.Mass = mass
        /// <summary>For support - updates a rope (fails automatically).</summary>
        /// <param name="ForceSet">The AccelerationManager which will be used to calculate new velocity and position.</param>
        /// <returns>Exception.</returns>
        /// <exception cref="System.Exception">Use the rope's Sync method member.</exception>
        member this.Sync forceSet time =
            failwith "Use the rope's Sync method member."
        /// <summary>For support - moves a rope (fails automatically).</summary>
        /// <param name="NewPosition">The new position as a tuple in centimeters.</param>
        /// <returns>Exception.</returns>
        /// <exception cref="System.Exception">Cannot move a Rope.</exception>
        member this.SetCarPosition _position =
            failwith "Cannot move a Rope."
        /// <summary>For support - moves a rope (fails automatically).</summary>
        /// <param name="NewPosition">The new position as an MVector of centimeters.</param>
        /// <returns>Exception.</returns>
        /// <exception cref="System.Exception">Cannot move a Rope.</exception>
        member this.SetPolPosition _position =
            failwith "Cannot move a Rope."
        /// <summary>Returns a new rope with the new color.</summary>
        /// <param name="NewColor">The new color.</param>
        /// <returns>The new rope as Object.</returns>
        member this.SetColor _color =
            RopeObject(sideA, sideB, mass, _color) :> Object
        /// <summary>Draws the rope using a given System.Drawing.Graphics.</summary>
        /// <param name="Graphics">The graphics which will be used to draw.</param>
        member this.Draw (g : Graphics) =
            use p = new Pen(color)
            g.DrawLine(p, point this.CarStart, point this.CarEnd)
    end

    /// <summary>Updates the ball's position and velocity to a given time with given set of forces.</summary>
    /// <param name="ForceSet">The AccelerationManager which will be used to calculate new velocity and position.</param>
    /// <returns>The updated hook.</returns>
    member this.Sync forceSet time sideAForces sideBForces =
        let _sideA = sideA.Sync sideAForces time
        let _sideB = sideB.Sync sideBForces time
        RopeObject(_sideA, _sideB, mass, color, lastUpdate)

    /// Up-casted to an Object.
    member this.Object() = this :> Object